<?php

/*
 * Copyright 2008 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#namespace tehframework\mvc;

#use tehframework\request\Request;
#use tehframework\response\Response;

class ControllerImpl extends ControllerBase
{
	const DEFAULT_ACTION_METHOD_NAME = 'handleRequest';
	
	/**
	 * @var EventHandler
	 */
	public $preExecute;
	
	/**
	 * @var EventHandler
	 */
	public $postExecute;
	
	/**
	 * @var ActionMethodResolver
	 */
	private $actionMethodResolver;
	
	/**
	 * @var ActionMethodInvoker
	 */
	private $actionMethodInvoker;
	
	/**
	 * @var object
	 */
	private $delegate;
	
	/**
	 * @var array
	 */
	private $defaultViewMappings = array();
	
	/**
	 * @param  ActionMethodResolver $actionMethodResolver
	 * @return ControllerImpl
	 */
	public function setActionMethodResolver(
		ActionMethodResolver $actionMethodResolver)
	{
		$this->actionMethodResolver = $actionMethodResolver;
		
		return $this;
	}
	
	/**
	 * @param  ActionMethodInvoker $actionMethodInvoker
	 * @return ControllerImpl
	 */
	public function setActionMethodInvoker(
		ActionMethodInvoker $actionMethodInvoker)
	{
		$this->actionMethodInvoker = $actionMethodInvoker;
		
		return $this;
	}
	
	/**
	 * @return object
	 */
	public function getDelegate()
	{
		return $this->delegate === null ? $this : $this->delegate;
	}
	
	/**
	 * @param  object $delegate
	 * @return ControllerImpl
	 */
	public function setDelegate($delegate)
	{
		$this->delegate = $delegate === $this ? null : $delegate;
		
		return $this;
	}
	
	/**
	 * @param  array<string,string> $defaultViewMappings
	 * @return ControllerImpl
	 */
	public function setDefaultViewMappings(array $defaultViewMappings)
	{
		$this->defaultViewMappings = $defaultViewMappings;
		
		return $this;
	}
	
	/**
	 * @param  Request $request
	 * @param  Response $response
	 * @return ModelAndView
	 * @throws Exception
	 */
	protected function execute(Request $request, Response $response)
	{
		$actionMethodName = $this->resolveActionMethodName($request);
		
		if (!$this->actionMethodInvoker)
		{
			if (isset($this->defaultViewMappings[$actionMethodName]))
			{
				return new ModelAndView($this->defaultViewMappings[$actionMethodName]);
			}
			
			throw new InvalidControllerStateException($this, sprintf(
				'<%s> can not handle the request, because the action method invoker ' .
				'was not provided and default view mapping for <%s> was not specified.',
				get_class($this->getDelegate()),
				$actionMethodName
			));
		}
		
		return $this->invokeActionMethod($actionMethodName, $request, $response);
	}
	
	/**
	 * @param  Request $request
	 * @return string
	 */
	private function resolveActionMethodName(Request $request)
	{
		if (!$this->actionMethodResolver)
		{
			return self::DEFAULT_ACTION_METHOD_NAME;
		}
		
		try
		{
			return $this->actionMethodResolver->getActionMethodName($request);
		}
		catch (ActionMethodNotFoundException $x)
		{
			return self::DEFAULT_ACTION_METHOD_NAME;
		}
	}
	
	/**
	 * @param  string $actionMethodName
	 * @param  Request $request
	 * @param  Response $response
	 * @return ModelAndView
	 * @throws Exception
	 */
	private function invokeActionMethod(
		$actionMethodName, Request $request, Response $response)
	{
		try
		{
			return $this->actionMethodInvoker->invokeActionMethod(
				$this->getDelegate(), $actionMethodName, $request, $response
			);
		}
		catch (InvalidActionMethodException $x)
		{
			if (isset($this->defaultViewMappings[$actionMethodName]))
			{
				return new ModelAndView($this->defaultViewMappings[$actionMethodName]);
			}
			
			throw new InvalidControllerStateException($this, sprintf(
				'<%s> can not handle the request, because no appropriate handler ' .
				'method was found and default view was not specified.',
				get_class($this->getDelegate())
			), $x);
		}
	}
}